/** * @Author: LTT 17386012013@163.com * @Date: 2024-10-15 22:54:45 * @Description: * @LastEditTime: 2024-10-15 22:54:45
* @LastEditors: LTT 17386012013@163.com */

<template>
  <view
    class="cropper-container"
    id="cropper-container">
    <canvas
      canvas-id="cropCanvas"
      class="crop-canvas"
      :style="{
        width: imgRect.oWidth + 'px',
        height: imgRect.oHeight + 'px',
      }"></canvas>
    <view
      id="main-container"
      ref="main-container"
      class="main-container"
      :style="{ width: mainRect.width + 'px', height: mainRect.height + 'px' }"
      @touchstart="touchstartHandle"
      @touchmove="touchmoveHandle"
      @touchend="touchendHandle">
      <image
        @load="imgLoad"
        class="target-image"
        :style="{
          width: imgRect.width + 'px',
          height: imgRect.height + 'px',
          left: imgRect.left + 'px',
          top: imgRect.top + 'px',
        }"
        :src="src" />

      <view class="cropper-helper"></view>
    </view>
  </view>
</template>

<script setup lang="ts">
  import { ref, getCurrentInstance, reactive, computed } from "vue";

  const props = defineProps<{
    src: string;
    destWidth?: number;
    destHeight?: number;
  }>();

  let touch: Touch | null;
  let touches: Touch[] = [];
  const mainRect = reactive({
    width: 0,
    height: 0,
  });
  const imgRect = reactive({
    oWidth: 0,
    oHeight: 0,
    width: 0,
    height: 0,
    left: 0,
    top: 0,
    scale: 1,
  });

  const src = ref<string>(props.src);
  const maskSize = ref(0.8);
  const maskWidth = computed(() => {
    return maskSize.value * 100 + "%";
  });

  let sideWidth: number;
  const ins = getCurrentInstance();
  const query = uni.createSelectorQuery().in(ins);
  const viewEl = query.select("#cropper-container");

  viewEl.boundingClientRect((res) => {
    if (!res) return;
    if (!Array.isArray(res)) {
      mainRect.width = res.width ?? 0;
      mainRect.height = res.height ?? 0;
      sideWidth = mainRect.width * maskSize.value;
    }
  });
  query.exec();

  const limitRect = computed(() => {
    return {
      maxLeft: mainRect.width * ((1 - maskSize.value) / 2),
      minLeft: mainRect.width * ((1 + maskSize.value) / 2) - imgRect.width,
      maxTop: (mainRect.height - sideWidth) / 2,
      minTop: (mainRect.height + sideWidth) / 2 - imgRect.height,
      minScale: Math.max(sideWidth / imgRect.oWidth, sideWidth / imgRect.oHeight),
      maxScale: 100,
    };
  });
  const ctx = uni.createCanvasContext("cropCanvas", ins);

  const scale = (delta: number) => {
    let width = imgRect.width;
    let height = imgRect.height;
    imgRect.scale += delta;
    imgRect.scale = Math.min(limitRect.value.maxScale, Math.max(imgRect.scale, limitRect.value.minScale));
    const originX = (mainRect.width / 2 - imgRect.left) / width;
    const originY = (mainRect.height / 2 - imgRect.top) / height;
    imgRect.width = imgRect.oWidth * imgRect.scale;
    imgRect.height = imgRect.oHeight * imgRect.scale;
    const deltaX = (imgRect.width - width) * originX;
    const deltaY = (imgRect.height - height) * originY;
    imgRect.left -= deltaX;
    imgRect.top -= deltaY;
  };

  const imgLoad = (e: Event) => {
    // @ts-ignore
    let { width, height } = e.detail;
    imgRect.oWidth = width;
    imgRect.oHeight = height;
    imgRect.width = width;
    imgRect.height = height;
    if (imgRect.width < sideWidth || imgRect.height < sideWidth) {
      imgRect.width = width * limitRect.value.minScale;
      imgRect.height = height * limitRect.value.minScale;
      imgRect.scale = limitRect.value.minScale;
    }
    imgRect.left = (mainRect.width - imgRect.width) / 2;
    imgRect.top = (mainRect.height - imgRect.height) / 2;
  };

  const limitRange = () => {
    imgRect.left = Math.min(imgRect.left, limitRect.value.maxLeft);
    imgRect.left = Math.max(imgRect.left, limitRect.value.minLeft);
    imgRect.top = Math.min(imgRect.top, limitRect.value.maxTop);
    imgRect.top = Math.max(imgRect.top, limitRect.value.minTop);
  };

  const touchstartHandle = (e: TouchEvent) => {
    if (e.touches.length == 1) {
      touch = e.touches[0];
    } else if (e.touches.length === 2) {
      touches[0] = e.touches[0];
      touches[1] = e.touches[1];
    }
  };
  const touchmoveHandle = (e: TouchEvent) => {
    if (e.touches.length === 1) {
      translateImage(e);
    } else if (e.touches.length === 2) {
      scaleImage(e);
    }
  };

  const touchendHandle = (e: TouchEvent) => {
    limitRange();
    touch = null;
    touches = [];
  };

  const translateImage = (e: TouchEvent) => {
    if (!touch) return;
    let t = e.touches[0];
    const deltaX = t.screenX - touch.screenX;
    const deltaY = t.screenY - touch.screenY;
    imgRect.left += deltaX;
    imgRect.top += deltaY;
    touch = t;
  };

  const scaleImage = (e: TouchEvent) => {
    if (touches[0] && touches[1]) {
      let t0 = e.touches[0];
      let t1 = e.touches[1];
      let delta =
        getDistanceOfPoints(touches[0].screenX, touches[0].screenY, touches[1].screenX, touches[1].screenY) -
        getDistanceOfPoints(t0.screenX, t0.screenY, t1.screenX, t1.screenY);
      touches[0] = t0;
      touches[1] = t1;
      scale(delta * -0.01);
    }
  };
  const getDistanceOfPoints = (x1: number, y1: number, x2: number, y2: number) => {
    return Math.abs(Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)));
  };

  const cropImage = async (): Promise<UniApp.CanvasToTempFilePathRes> => {
    return new Promise((resolve, reject) => {
      ctx.drawImage(src.value, 0, 0);
      ctx.draw(false, () => {
        uni.canvasToTempFilePath(
          {
            x: (limitRect.value.maxLeft - imgRect.left) / imgRect.scale,
            y: (limitRect.value.maxTop - imgRect.top) / imgRect.scale,
            width: (mainRect.width * maskSize.value) / imgRect.scale,
            height: (mainRect.width * maskSize.value) / imgRect.scale,
            destWidth: props.destWidth ?? 200,
            destHeight: props.destHeight ?? 200,
            canvasId: "cropCanvas",
            success(res) {
              resolve(res);
            },
            fail(result) {
              reject(result);
            },
          },
          ins
        );
      });
    });
  };
  defineExpose({
    cropImage,
  });
</script>

<style>
  .cropper-container {
    width: 100vw;
    height: 100vh;
    /* background-color: orangered; */
    overflow: hidden;
    position: relative;
  }
  .main-container {
    overflow: hidden;
    position: relative;
    background: #000;
  }

  .crop-canvas {
    opacity: 0;
    position: absolute;
    z-index: -1;
    left: 100vw;
    top: 100vh;
  }

  .target-image {
    position: absolute;
  }

  .cropper-helper {
    width: v-bind(maskWidth);
    aspect-ratio: 1;
    border-radius: 1800rpx;
    background: rgba(0, 0, 0, 0.1);
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    border: 800rpx solid rgba(0, 0, 0, 0.75);
  }
</style>
